#region License
//-----------------------------------------------------------------------------
// Copyright (c) 2008, Aaron MacDougall, Daniel Jeffery
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
//   this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright notice,
//   this list of conditions and the following disclaimer in the documentation
//   and/or other materials provided with the distribution.
//
// * Neither the name of Aaron MacDougall or Daniel Jeffery nor the names of its contributors may
//   be used to endorse or promote products derived from this software without
//   specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//-----------------------------------------------------------------------------
#endregion

#region Using Statements
using System;
using System.Diagnostics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
#endregion

namespace Agro2D
{
    /// <summary>
    /// Represents a 2D camera that manages the viewport to the world.
    /// </summary>
    public class Camera
    {
        #region Fields
        private Vector2 position;
        private Rectangle screenLocation;
        protected GraphicsDeviceManager graphics;
        protected RenderTarget2D renderTarget;
        #endregion

        #region Properties
        /// <summary>
        /// Get/Set the Camera position in the game world.
        /// </summary>
        public Vector2 Position
        {
            get { return this.position; }
            set { this.position = value; }
        }

        /// <summary>
        /// Get/Set the position of the camera on the screen.
        /// </summary>
        public Rectangle ScreenLocation
        {
            get { return this.screenLocation; }
            set
            {
                Debug.Assert(value.X >= 0);
                Debug.Assert(value.Right <= this.graphics.GraphicsDevice.Viewport.Width);
                Debug.Assert(value.Y >= 0);
                Debug.Assert(value.Bottom <= this.graphics.GraphicsDevice.Viewport.Height);
                Debug.Assert(value.Width > 0);
                Debug.Assert(value.Height > 0);

                this.screenLocation = value;
                CreateRenderTarget();
            }
        }
        #endregion

        #region Constructors/Destructors
        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="graphics">
        /// GraphicsDeviceManager to use for render targets and accessing back
        /// buffer sizes.
        /// </param>
        public Camera(GraphicsDeviceManager graphics)
        {
            this.graphics = graphics;
            this.position = Vector2.Zero;
            this.screenLocation = new Rectangle(
                0,
                0,
                graphics.PreferredBackBufferWidth,
                graphics.PreferredBackBufferHeight
                );
        }

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="graphics">
        /// GraphicsDeviceManager to use for render targets and accessing back
        /// buffer sizes.
        /// </param>
        /// <param name="screenLocation">
        /// The position of the camera on the screen.
        /// </param>
        public Camera(GraphicsDeviceManager graphics, Rectangle screenLocation)
        {
            this.graphics = graphics;
            this.position = Vector2.Zero;
            ScreenLocation = screenLocation;
        }
        #endregion

        /// <summary>
        /// Call from Game.LoadContent() to recreate render target when the
        /// graphics device is reset.
        /// </summary>
        public void LoadContent()
        {
            CreateRenderTarget();
        }

        /// <summary>
        /// Creates a new render target based on the current viewport.
        /// </summary>
        protected virtual void CreateRenderTarget()
        {
            this.renderTarget = new RenderTarget2D(
                    this.graphics.GraphicsDevice,
                    this.screenLocation.Width,
                    this.screenLocation.Height,
                    1,
                    SurfaceFormat.Color
                    );
        }

        /// <summary>
        /// Updates the Camera position. Doesn't do anything in this base
        /// implementation, but can be used for camera movement in
        /// derrived classes.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        public virtual void Update(GameTime gameTime)
        {
        }

        /// <summary>
        /// Draws the specified SpriteManager inside this camera.
        /// </summary>
        /// <param name="spriteManager">SpriteManager to draw.</param>
        public virtual void Draw(SpriteManager spriteManager)
        {
            Debug.Assert(spriteManager != null);
            Debug.Assert(this.renderTarget != null);

            // Get the current backbuffer because XNA doesn't save it when
            // switching render targets.
            ResolveTexture2D backBuffer = new ResolveTexture2D(
                this.graphics.GraphicsDevice,
                graphics.PreferredBackBufferWidth,
                graphics.PreferredBackBufferHeight,
                1,
                SurfaceFormat.Color
                );
            this.graphics.GraphicsDevice.ResolveBackBuffer(backBuffer);

            // Set to draw to our render target
            this.graphics.GraphicsDevice.SetRenderTarget(0, this.renderTarget);
            this.graphics.GraphicsDevice.Clear(new Color(0, 0, 0, 0));

            // Draw
            using (SpriteBatch spriteBatch = new SpriteBatch(this.graphics.GraphicsDevice))
            {
                spriteBatch.Begin();

                Rectangle viewPort = this.screenLocation;
                viewPort.X += (int)(this.position.X + 0.5f);
                viewPort.Y += (int)(this.position.Y + 0.5f);

                // Draw sprites
                spriteManager.Draw(spriteBatch, ref viewPort, 1);

                spriteBatch.End();

                // Obtain rendered texture
                this.graphics.GraphicsDevice.SetRenderTarget(0, null);

                // Draw the saved back buffer, then our render target on top
                spriteBatch.Begin();
                spriteBatch.Draw(backBuffer, Vector2.Zero, Color.White);
                spriteBatch.Draw(this.renderTarget.GetTexture(), Vector2.Zero, Color.White);
                spriteBatch.End();
            }
        }

        /// <summary>
        /// Moves the Camera by the specified amount.
        /// </summary>
        /// <param name="xAmount">Amount to move on the x-axis.</param>
        /// <param name="yAmount">Amount to move on the y-axis.</param>
        public virtual void MoveBy(float xAmount, float yAmount)
        {
            this.position.X += xAmount;
            this.position.Y += yAmount;
        }

        /// <summary>
        /// Moves the Camera to the specified position in the world.
        /// </summary>
        /// <param name="xPosition">X-position to move to.</param>
        /// <param name="yPosition">Y-position to move to.</param>
        public virtual void MoveTo(float xPosition, float yPosition)
        {
            this.position.X = xPosition;
            this.position.Y = yPosition;
        }
    }
}